/*
* Copyright 2011 ClamShell-Cli.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.clamshellcli.jmx;
import org.clamshellcli.api.Context;
import org.clamshellcli.core.ShellException;
import com.sun.tools.attach.VirtualMachine;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.remote.JMXServiceURL;
import sun.jvmstat.monitor.HostIdentifier;
import sun.jvmstat.monitor.MonitoredHost;
import sun.jvmstat.monitor.MonitoredVm;
import sun.jvmstat.monitor.VmIdentifier;
import sun.management.ConnectorAddressLink;
/**
* This is a helper class to aggregate common utility tools for different
* management tools such as vmstat, attach api, and JMX api.
* @author vladimir.vivien
*/
public final class Management {
public static final String VALUE_LOCALHOST = "localhost";
public static final String KEY_ARGS_HOST = "host";
public static final String KEY_ARGS_PID = "pid";
public static final String KEY_VMINFO_MAP = "key.vminfo.map";
public static final String KEY_JMX_MBEANSERVER = "key.jmx.mbServer";
public static final String KEY_JMX_CONNECTOR = "key.jmx.connector";
public static final String KEY_JMX_URL = "key.jmx.url";
public static final String KEY_MBEANS_MAP = "key.mbeans.map";
public static final String KEY_DEFAULT_MBEANS = "key.default.mbeans";
protected static final String VALUE_FILE_SEP = File.separator;
protected static final String VALUE_AGENT_JAR = "management-agent.jar";
protected static final String VALUE_AGENT_INIT_PROP = "com.sun.management.jmxremote";
protected static final String VALUE_AGENT_CONNECTOR_PROP = "com.sun.management.jmxremote.localConnectorAddress";
private Management(){}
/**
* Returns the host address from the passed argument map.
* @param argsMap a Map<String,String> for the argument values
* @return the host address if in argument or "localhost" if none.
*/
public static String getHostFromArgs(Map<String,Object> argsMap){
return (argsMap != null && argsMap.get(KEY_ARGS_HOST) != null) ?
(String)argsMap.get(KEY_ARGS_HOST) : "localhost";
}
/**
* Returns HostIdentifier from the string form of hostName.
* @param hostName
* @return HostIdentifier
*/
public static HostIdentifier getHostIdentifier(String hostName){
HostIdentifier hostIdentifier = null;
try {
hostIdentifier = new HostIdentifier((hostName != null) ? hostName : "localhost");
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return hostIdentifier;
}
/**
* Returns a MonitoredVm instance from the provided MonitoredHost and the
* jvm process id.
* @param mHost MonitoredHost instance
* @param jvmId jvm process id.
* @return MonitoredVm
* @throws Exception if there is a problem.
*/
public static MonitoredVm getMonitoredVm(MonitoredHost mHost, Integer jvmId) throws Exception{
String vmUri = "//" + jvmId + "?mode=r";
VmIdentifier vmId = new VmIdentifier(vmUri);
return mHost.getMonitoredVm(vmId,0);
}
/**
* Creates a Map<Integer,Management.LocalJVMInfo> map (VmMap) with information from
* specified host.
* as a key.
* @param String host address of VMs to map
* @throws Exception if something bad happens.
*/
public static Map<Integer, Management.VmInfo> mapVmInfo(String hostAddr) throws Exception{
Map<Integer, Management.VmInfo> map = new LinkedHashMap<Integer,Management.VmInfo>();
HostIdentifier hostIdentifier = Management.getHostIdentifier(hostAddr);
MonitoredHost monitoredHost = MonitoredHost.getMonitoredHost(hostIdentifier);
Set<Integer> jvmIds = monitoredHost.activeVms();
// harvest vm info from host
for (Integer jvmId : jvmIds) {
MonitoredVm monitoredVm = Management.getMonitoredVm(monitoredHost, jvmId);
// save vm info.
map.put(
jvmId, new Management.VmInfo(monitoredVm)
);
monitoredVm.detach();
}
return map;
}
/**
* Returns a MonitoredVm instance based on the localVm process id
* @param id process id associated with vm
* @return MonitoredVm
* @throws Exception if something goes wrong
*/
public static MonitoredVm getMonitoredVmFromId(int id) throws Exception{
HostIdentifier hostIdentifier = Management.getHostIdentifier(Management.VALUE_LOCALHOST);
MonitoredHost monitoredHost = MonitoredHost.getMonitoredHost(hostIdentifier);
return Management.getMonitoredVm(monitoredHost, id);
}
//private static Pattern defaultAddrPattern = Pattern.compile(".+");
private static Pattern simpleAddrPattern = Pattern.compile(".+:[0-9]+");
/**
* Returns a fully constructed JMXServiceURL based on passed address.
* It accepts default form "hostname", "hostname:port", or the verobse form
* "service:jmx:rmi://host:port/jmxrmi". If the param is not the first two
* it assumes the verbose form.
* @param hostUrl - a String form of the host address.
* @return JMXServiceURL.
*/
public static JMXServiceURL getJmxUrlFrom(String hostUrl) throws Exception{
if(hostUrl == null || hostUrl.isEmpty())
return null;
String urlString = hostUrl;
// if scheme,protocol, port omitted, assume "localhost"
if(hostUrl.equalsIgnoreCase(VALUE_LOCALHOST)){
urlString = "service:jmx:rmi:///jndi/rmi://" +
hostUrl + ":1099/jmxrmi";
}
// if host:port provided, decorate with scheme,protocol, and path
if(simpleAddrPattern.matcher(hostUrl).matches()){
urlString = "service:jmx:rmi:///jndi/rmi://" +
hostUrl + "/jmxrmi";
}
// else use url as provided
JMXServiceURL svcUrl = null;
try {
svcUrl = new JMXServiceURL(urlString);
} catch (MalformedURLException ex) {
throw new Exception(ex);
}
return svcUrl;
}
/**
* This method returns the Connector address exported by the agent.
* @param vm process id
*/
public static String getLocalVmAddress(VirtualMachine vm) throws Exception{
// load management-agent.jar from java_home/lib/
String homeDir = vm.getSystemProperties().getProperty("java.home");
String agentPath =
homeDir + Management.VALUE_FILE_SEP +
"lib" + Management.VALUE_FILE_SEP +
Management.VALUE_AGENT_JAR;
File agentFile = new File(agentPath);
if(!agentFile.exists() || !agentFile.isFile()){
throw new Exception("Unable to find management agent file.");
}
vm.loadAgent(agentFile.getAbsolutePath(), Management.VALUE_AGENT_INIT_PROP);
Properties agentProps = vm.getAgentProperties();
String address = (String) agentProps.get(Management.VALUE_AGENT_CONNECTOR_PROP);
return address;
}
public static void verifyServerConnection(Context ctx) throws ShellException{
MBeanServerConnection server = (MBeanServerConnection)ctx.getValue(Management.KEY_JMX_MBEANSERVER);
if(server == null){
throw new ShellException("No JMX server connection found. "
+ "Connect to a JMX server first (see help).");
}
}
/**
* Convenience wrapper for MBeanServerConnection.getObjectInstance().
* It rolls up all exception into ShellException.
* @param server
* @param nameStr
* @return
* @throws ShellException
*/
public static ObjectInstance getObjectInstance(MBeanServerConnection server, String nameStr) throws ShellException{
ObjectInstance result = null;
try {
result = server.getObjectInstance(new ObjectName(nameStr));
} catch (MalformedObjectNameException ex) {
throw new ShellException(ex);
} catch (NullPointerException ex) {
throw new ShellException(ex);
} catch (InstanceNotFoundException ex) {
throw new ShellException(ex);
} catch (IOException ex) {
throw new ShellException(ex);
}
return result;
}
/**
* Returns a collection of fully-realized object instances.
* It uses the server instance to retrieve the instances if they exist.
* @param server - MBeanServerConnection instance
* @param nameStr - ObjectName expression used to retrieve beans
* @return ObjectInsatnce[] a collection of ObjectInstance
* @throws ShellException - if anything is not correct.
*/
public static ObjectInstance[] getObjectInstances(MBeanServerConnection server, String nameStr) throws ShellException{
ObjectInstance[] result = null;
try {
ObjectName objName = new ObjectName(nameStr);
Set<ObjectInstance> objs = server.queryMBeans(objName, null);
result = (objs != null) ? objs.toArray(new ObjectInstance[]{}) : null;
} catch (IOException ex) {
throw new ShellException(ex);
} catch (MalformedObjectNameException ex) {
throw new ShellException(ex);
} catch (NullPointerException ex) {
throw new ShellException(ex);
}
return result;
}
/**
* Look for object instances in cache first. Then, if none is found,
* load object instances from mbean server.
* @param ctx - Context
* @param name - the ObjectName or the label used for caching the instance
* @return - one or more instances that may match the name
* @throws ShellException
*/
public static ObjectInstance[] findObjectInstances(Context ctx, String name) throws ShellException {
Map<String, ObjectInstance> map = (Map<String, ObjectInstance>) ctx.getValue(Management.KEY_MBEANS_MAP);
if (name == null) {
if (map == null || map.get(Management.KEY_DEFAULT_MBEANS) == null) {
throw new ShellException(String.format("You must specify MBean(s) for command or "
+ "set a default MBean using 'mbean' command (see help)."));
}
return new ObjectInstance[]{map.get(Management.KEY_DEFAULT_MBEANS)};
} else {
ObjectInstance[] objs = null;
if(map != null && map.get(name) != null){
objs = new ObjectInstance[]{map.get(name)};
}else{
MBeanServerConnection conn = (MBeanServerConnection) ctx.getValue(Management.KEY_JMX_MBEANSERVER);
objs = Management.getObjectInstances(conn, name);
}
return objs;
}
}
/**
* A domain class to cache information for discovered local JVM instances.
*
*/
public static class VmInfo{
private MonitoredVm monitoredVm;
private String address;
private boolean attachable;
public VmInfo(MonitoredVm mVm){
assert mVm != null;
assert mVm.getVmIdentifier() != null;
this.monitoredVm = mVm;
int vmId = monitoredVm.getVmIdentifier().getLocalVmId();
try{
VirtualMachine vm = VirtualMachine.attach(String.valueOf(vmId));
address = Management.getLocalVmAddress(vm);
attachable = (address != null);
}catch(Exception ex){
try {
address = ConnectorAddressLink.importFrom(vmId);
} catch (IOException ex1) {
throw new ShellException (ex1);
}
}finally{
mVm.detach();
}
}
public String getAddress() {
return address;
}
public boolean isAttachable() {
return attachable;
}
public MonitoredVm getMonitoredVm() {
return monitoredVm;
}
}
}